package com.skillworld.webapp.model.userservice;



import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.skillworld.webapp.model.bag.BagDao;
import com.skillworld.webapp.model.department.Department;
import com.skillworld.webapp.model.department.DepartmentDao;
import com.skillworld.webapp.model.facebookservice.FacebookService;
import com.skillworld.webapp.model.facebookservice.FacebookServiceMock;
import com.skillworld.webapp.model.gamemap.GameMapDao;
import com.skillworld.webapp.model.gameservice.NoLevelMapException;
import com.skillworld.webapp.model.team.Team;
import com.skillworld.webapp.model.team.TeamDao;
import com.skillworld.webapp.model.tutorialservice.Query;
import com.skillworld.webapp.model.user.User;
import com.skillworld.webapp.model.user.UserDao;
import com.skillworld.webapp.model.util.InvalidArgumentException;
import com.skillworld.webapp.model.util.PermissionDeniedException;

import es.udc.pojo.modelutil.exceptions.InstanceNotFoundException;

@Service("UserService")
@Transactional
public class UserServiceImpl implements UserService
{
    
    public static final int INITIAL_BAG_CAPACITY = 5;
    public static final int MAX_COUNT = 100;
    
    
    // DAOs
	
    @Autowired
    private UserDao userDao;
    
    @Autowired
    private TeamDao teamDao;
    
    @Autowired
    private GameMapDao gameMapDao;
    
    @Autowired
    private DepartmentDao departmentDao;
    
    @Autowired
    private BagDao bagDao;
    
    // Facebook
    
    FacebookService facebook = new FacebookServiceMock();
    
 
    
    public User registerFacebookUser(String facebookId, String authorizationToken, 
                                        String nickname, String skilly, String picture)
        throws AlreadyRegisteredException, NicknameAlreadyInUseException,
                InvalidFacebookTokenException
    {
        if (userDao.existsByNickname(nickname))
        {
            throw new NicknameAlreadyInUseException(nickname);
        }
        
        if (userDao.existsByFacebookId(facebookId))
        {
            throw new AlreadyRegisteredException(facebookId);
        }
        
        if (!facebook.isValidAuthorizationToken(facebookId, authorizationToken))
        {
            throw new InvalidFacebookTokenException(facebookId, authorizationToken);
        }
 
        User user = new User(nickname, skilly, picture, facebookId, 
                                INITIAL_BAG_CAPACITY, 
                                gameMapDao.getMainMap());
        userDao.save(user);
        
        return user;
    }
  
    @Transactional(readOnly=true)
    public User facebookUserLogin(String facebookId, String authorizationToken)
        throws InvalidFacebookTokenException, InstanceNotFoundException
    {
        /*
         * First, we try to find the user, which is a local operation, much cheaper
         * than a call to the Facebook API.
         */
        
        User user = userDao.findByFacebookId(facebookId);
        
        if(!facebook.isValidAuthorizationToken(facebookId, authorizationToken)) 
        {
            throw new InvalidFacebookTokenException(facebookId, authorizationToken);
        }
        
        return user;
    }
	
    @Transactional(readOnly=true)
	public User findUserProfile(Long userId)
	        throws InstanceNotFoundException
	{
		return userDao.findById(userId);
	}
	
	public void updateUserProfile(Long userId, String newNickname, 
	                                String newSkilly, String newPicture)
	    throws InstanceNotFoundException, NicknameAlreadyInUseException
	{
		User user = userDao.findById(userId);
		
		    // IDEA: We may define a custom Nickname type
		if (!user.getNickname().toUpperCase().equals(newNickname.toUpperCase()) 
		        && userDao.existsByNickname(newNickname))
		{
		    throw new NicknameAlreadyInUseException(newNickname);
		}

		user.setNickname(newNickname);
		user.setSkilly(newSkilly);
		user.setPicture(newPicture);
	}
	
	@Transactional(readOnly=true)
	public List<User> findUsers(String nickPrefix, 
	            Long inTeam, Long notInTeam, List<Long> exclude, 
	            int startIndex, int count) 
            throws InvalidArgumentException
	{
	    if (count < 1 || count > MAX_COUNT)
	    { throw new InvalidArgumentException(); }
	    
	    return userDao.findByNicknamePrefix(nickPrefix, 
	                    inTeam, notInTeam, exclude, startIndex, count);
	}
	
	@Transactional(readOnly=true)
	public int numberOfUsers(String nickPrefix, 
	        Long inTeam, Long notInTeam, List<Long> exclude)
    { 
	    return(int) userDao.numberOfUsersByNicknamePrefix(nickPrefix, 
	                                        inTeam, notInTeam, exclude); 
	}
	
	@Transactional(readOnly=true)
	public List<User> topUsers(int count)
	    throws InvalidArgumentException
	{
	    if (count < 1 || count > MAX_COUNT)
	    { throw new InvalidArgumentException(); }
	    
	    return userDao.topUsers(count);
	}
	
	// FIX (specializing?): InstanceNotFoundException may have multiple causes
    public Team createTeam(String name, String info,
            String image, long leaderId, List<Long> deptIds)
            throws InstanceNotFoundException, TeamNameAlreadyInUseException, InvalidArgumentException 
    {
        if (deptIds.size() < 1 || deptIds.size() > 3)
        { throw new InvalidArgumentException(); }
        
        if (teamDao.existsByName(name))
        { throw new TeamNameAlreadyInUseException(name); }
        
        User leader = userDao.findById(leaderId);
        
        Set<Department> departments = new HashSet<Department>();
        for (long deptId : deptIds)
        { departments.add(departmentDao.findById(deptId)); }
        
        Team newTeam = new Team(name, info, image, leader, departments);
        teamDao.save(newTeam);
        
        return newTeam;
    }

	public Team findTeam(Long teamId) throws InstanceNotFoundException 
	{
		return teamDao.findById(teamId);
	}
	
	public List<Team> findUserTeams(long userId) throws InstanceNotFoundException
	{
	    User user = userDao.findById(userId);
	    List<Team> teams = new ArrayList<Team>(user.getTeams());
	    return teams;
	}
	
    public List<Team> searchTeams(String query, int startIndex, int count)
    {
        return teamDao.searchTeams(Query.parseQuery(query), 
                                    startIndex, count);
    }
    
    public int numberOfTeams(String query)
    {
        return(int) teamDao.numberOfTeams(Query.parseQuery(query));
    }
	
	public void updateTeamProfile(Long teamId, Long userId, 
	                    String newName, String newInfo, String newImage)
	        throws InstanceNotFoundException, PermissionDeniedException, 
	                TeamNameAlreadyInUseException
	{
		Team team = teamDao.findById(teamId);
		
		if(!team.getLeader().getUserId().equals(userId))
		{ throw new PermissionDeniedException(); }
		
		if (!team.getName().equals(newName) && teamDao.existsByName(newName))
		{ throw new TeamNameAlreadyInUseException(newName); }

		team.setName(newName);
		team.setInfo(newInfo);
		team.setImage(newImage);
	}
	
	// FIX (specializing?): InstanceNotFoundException may have multiple causes
    public void inviteToJoinTeam(long teamId, long inviterId, long inviteeId) 
            throws InstanceNotFoundException, PermissionDeniedException,
                    AlreadyTeamMemberException
    {
        List<Long> inviteeIds = new ArrayList<Long>();
        inviteeIds.add(inviteeId);
        inviteToJoinTeam(teamId, inviterId, inviteeIds);
    }
    
    public void inviteToJoinTeam(long teamId, long inviterId, List<Long> inviteeIds) 
        throws InstanceNotFoundException, PermissionDeniedException,
                AlreadyTeamMemberException
    {
        Team team = teamDao.findById(teamId);
        
        if(!team.getLeader().getUserId().equals(inviterId))
        { throw new PermissionDeniedException(); }
        
        List<User> invitees = new ArrayList<User>();
        for (long inviteeId : inviteeIds)
        {
            User invitee = userDao.findById(inviteeId);
            
            if (invitee.getTeams().contains(team))
            { throw new AlreadyTeamMemberException(team, invitee); }
            
            invitees.add(invitee);
        }
        
        for (User invitee : invitees)
        { team.addMember(invitee); }
    }
	
    public void removeFromTeam(long teamId, long leaderId, long memberId) 
        throws InstanceNotFoundException, PermissionDeniedException, 
                NotTeamMemberException
    { 
        List<Long> memberIds = new ArrayList<Long>();
        memberIds.add(memberId);
	    removeFromTeam(teamId, leaderId, memberIds);
	}
    
    public void removeFromTeam(long teamId, long leaderId, List<Long> memberIds) 
            throws InstanceNotFoundException, PermissionDeniedException, 
                    NotTeamMemberException
    {
        Team team = teamDao.findById(teamId);
        
        if(!team.getLeader().getUserId().equals(leaderId))
        { throw new PermissionDeniedException(); }        
        
        List<User> members = new ArrayList<User>();
        for (long memberId : memberIds)
        { 
            // Leader cannot remove himself
            if (leaderId == memberId)
            { throw new PermissionDeniedException(); }
            
            members.add(userDao.findById(memberId)); 
        }
        
        for (User member : members)
        {
            if(!team.getMembers().contains(member))
            { throw new NotTeamMemberException(member.getUserId(), teamId); }    
        }
        
        for (User member : members)
        { team.removeMember(member); }
    }
	 
	public void leaveTeam(Long teamId, Long userId)
	    throws InstanceNotFoundException, PermissionDeniedException, 
	            NotTeamMemberException
	{
	    Team team = teamDao.findById(teamId);
	    
        if(team.getLeader().getUserId().equals(userId))
        { throw new PermissionDeniedException(); }
        
        User user = userDao.findById(userId);
        
        if (!user.getTeams().contains(team))
        { throw new NotTeamMemberException(userId, teamId); }
		
		team.removeMember(user);		
	}

	public List<Team> topTeams(int count)
       throws InvalidArgumentException
    {
        if (count < 1 || count > MAX_COUNT)
        { throw new InvalidArgumentException(); }
	    
	    return teamDao.topTeams(count);
    }
}
